/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef MESH_HH_
#define MESH_HH_

#include "carina/math.hh"
#include "carina/scene.hh"
#include "carina/common/timer.hh"
#include "carina/common/global.hh"
#include "carina/common/containers.hh"
#include "carina/common/types.hh"
#include "carina/material-manager.hh"

#include <vector>

namespace Carina
{
class Mesh;
class Material;
class MeshLoaderBase;
class Drawable;

typedef shared_ptr<MeshLoaderBase> MeshLoaderPtr;
typedef shared_ptr<Mesh> MeshPtr;

typedef DynamicArray<DualQuaternion> DualQuatArray;

struct KeyFrame
{
    float               Time;
    DualQuatArray       TransformQuats;

    KeyFrame() {}
    KeyFrame(size_t bones)
        :   TransformQuats(bones) {}
};

typedef DynamicArray<KeyFrame> AnimationData;

class Animation
{
    string              m_Name;
    AnimationData       m_Data;
public:
    Animation();
    Animation(const string& name, const AnimationData& data);
    Animation(const Animation& anim);

    Animation& operator=(const Animation& anim);

    bool operator==(const string& name) const { return name == m_Name; }

    float endTime() const;

    void getTransform(float t, DualQuatArray& tdq, bool repeat) const;
};

// TODO: Repetitive
class Bone
{
    DualQuaternion          m_InitialPose;
    DualQuaternion          m_InvBindTransform;
    DynamicArray<size_t>    m_Children;
public:
    size_t children() const { return m_Children.size(); }
    size_t getChild(size_t idx) const { return m_Children[idx]; }
    void addChild(size_t idx) { m_Children.push_back(idx); }
    void setInvBindTransform(const DualQuaternion& dq) { m_InvBindTransform = dq; }
    void setInitial(const DualQuaternion& dq) { m_InitialPose = dq; }
    DualQuaternion getInvBindTransform() const { return m_InvBindTransform; }
    DualQuaternion getInitial() const { return m_InitialPose; }
};

class Skeleton
{
    size_t                  m_Root;
    DynamicArray<Bone>      m_Nodes;
    size_t                  m_BoneCount;
public:
    void setRoot(size_t idx) { m_Root = idx; }
    Bone& root() { return m_Nodes[m_Root]; }
    const Bone& root() const { return m_Nodes[m_Root]; }

    bool hasBones() const { return m_Nodes.size() > 0; }
    void setCount(size_t bone_count, size_t node_count) { m_BoneCount = bone_count; m_Nodes.resize(node_count); }
    size_t getBoneCount() const { return m_BoneCount; }
    size_t getNodeCount() const { return m_Nodes.size(); }

    Bone& getBone(size_t i) { return m_Nodes[i]; }
    const Bone& getBone(size_t i) const { return m_Nodes[i]; }

    void skinning(const DualQuatArray& in_pose, DualQuatArray& out_smat);
    void getInitialSkinning(DualQuatArray& smat);
};

class Material;

class Submesh
{
    unsigned        m_Offset;
    unsigned        m_Count;
    Material        m_Material;
    InputLayoutPtr  m_InputLayout;
public:
    Submesh(const RendererPtr& renderer, const Material& mat, unsigned off, unsigned count, bool skel);

    Material& getMaterial();
    void draw(const Scene& scene, const Matrix4& mat);
};

typedef DynamicArray<Submesh> SubmeshArray;
typedef DynamicArray<Animation> AnimationArray;

typedef std::vector<uint32> IndicesArray;
typedef std::vector<float>  PointDataArray;

class Mesh
{
    VideoBufferPtr      m_VertsBuffer;
    VideoBufferPtr      m_IndicesBuffer;
    SubmeshArray        m_Submeshes;

    Skeleton            m_Skeleton;
    AnimationArray      m_Animations;

    bool                m_Repeat;
    Timer               m_AnimTimer; // TODO: naive
    Animation           *m_CurrentAnimation;
public:
    Mesh();
    Mesh(const RendererPtr& renderer, const SubmeshArray& submesh, 
		 const PointDataArray& point_data,
         const IndicesArray& indices,
         const Skeleton& skel=Skeleton());
     ~Mesh();

    bool playAnimation(const string& name, bool repeat=false);
    void draw(Scene& scene, const Matrix4& mat);
    void addAnimation(const Animation& anim);
    void addAnimations(const AnimationArray& anims);
};

typedef shared_ptr<Mesh> MeshPtr;

class ResourceManager;

class MeshLoaderBase
{
public:
    virtual ~MeshLoaderBase() {}

    virtual MeshPtr load(const string& file, ResourceManager& ResMgr)=0;

    virtual void loadTriangles(const string& filename, DynamicArray<float>& vertices, DynamicArray<uint32>& indices)=0;
};

const unsigned max_bones_per_vert = 4;
}

#endif /* MESH_HH_ */
